﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Text;

namespace Apewer.Source
{

    /// <summary>ORM 帮助程序。</summary>
    public static class SourceUtility
    {

        #region ORM

        /// <summary>读取所有行，生成列表。</summary>
        public static T[] Fill<T>(this IQuery query) where T : class, new()
        {
            var objects = Fill(query, typeof(T));
            var array = CollectionUtility.As<object, T>(objects);
            return array;
        }

        /// <summary>读取所有行填充到 T，组成 T[]。</summary>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        public static object[] Fill(this IQuery query, Type model)
        {
            if (query == null) return new object[0];
            if (query.Table == null) return new object[0];
            if (model == null) return new object[0];

            return Fill(query.Table, model);
        }

        /// <summary>将 Query 的行，填充到模型实体。</summary>
        /// <remarks>填充失败时返回 NULL 值。</remarks>
        /// <exception cref="Exception"></exception>
        public static object FillRow(IQuery query, int rowIndex, Type model, TableStructure structure) => FillRow(query?.Table, rowIndex, model, structure);

        /// <summary>将 Query 的行，填充到模型实体。</summary>
        /// <remarks>填充失败时返回 NULL 值。</remarks>
        /// <exception cref="Exception"></exception>
        public static object FillRow(DataTable table, int rowIndex, Type model, TableStructure structure)
        {
            // 检查参数。
            if (table == null || model == null || structure == null) return null;
            if (rowIndex < 0 || rowIndex >= table.Rows.Count) return null;
            if (!RuntimeUtility.CanNew(model)) return null;

            // 变量别名。
            var ts = structure;
            var r = rowIndex;
            var columns = ts.Columns;

            // 检查模型的属性，按属性从表中取相应的列。
            var record = Activator.CreateInstance(model);
            var properties = model.GetProperties();
            foreach (var property in properties)
            {

                // 在表结构中检查，是否包含此属性，并获取 ColumnAttribute 中的 Field。
                var field = null as string;
                for (var j = 0; j < columns.Length; j++)
                {
                    if (columns[j].PropertyName == property.Name)
                    {
                        field = columns[j].Field;
                        break;
                    }
                }
                if (field == null)
                {
                    if (ts && ts.Table.AllProperties) continue;
                    field = property.Name;
                }


                var value = table.Rows[r][field];
                if (value != null && value.Equals(DBNull.Value)) value = null;
                var setted = Set(record, property, value);
            }
            return record;
        }

        static bool Set(object record, PropertyInfo property, object value)
        {
            // 读取值。
            if (value == null) return false;
            if (value.Equals(DBNull.Value)) return false;

            // 必须有 setter 访问器。
            var setter = property.GetSetMethod();
            if (setter == null) return false;

            // 根据属性类型设置值。
            var pt = property.PropertyType;
            if (pt.Equals(typeof(object))) setter.Invoke(record, new object[] { value });
            else if (pt.Equals(typeof(byte[]))) setter.Invoke(record, new object[] { (byte[])value });
            else if (pt.Equals(typeof(string))) setter.Invoke(record, new object[] { value.ToString() });

            else if (pt.Equals(typeof(DateTime))) setter.Invoke(record, new object[] { value });
            else if (pt.Equals(typeof(bool))) setter.Invoke(record, new object[] { NumberUtility.Boolean(value) });
            else if (pt.Equals(typeof(byte))) setter.Invoke(record, new object[] { NumberUtility.Byte(value) });
            else if (pt.Equals(typeof(sbyte))) setter.Invoke(record, new object[] { NumberUtility.SByte(value) });
            else if (pt.Equals(typeof(short))) setter.Invoke(record, new object[] { NumberUtility.Int16(value) });
            else if (pt.Equals(typeof(ushort))) setter.Invoke(record, new object[] { NumberUtility.UInt16(value) });
            else if (pt.Equals(typeof(int))) setter.Invoke(record, new object[] { NumberUtility.Int32(value) });
            else if (pt.Equals(typeof(uint))) setter.Invoke(record, new object[] { NumberUtility.UInt32(value) });
            else if (pt.Equals(typeof(long))) setter.Invoke(record, new object[] { NumberUtility.Int64(value) });
            else if (pt.Equals(typeof(ulong))) setter.Invoke(record, new object[] { NumberUtility.UInt64(value) });
            else if (pt.Equals(typeof(float))) setter.Invoke(record, new object[] { NumberUtility.Single(value) });
            else if (pt.Equals(typeof(double))) setter.Invoke(record, new object[] { NumberUtility.Double(value) });
            else if (pt.Equals(typeof(decimal))) setter.Invoke(record, new object[] { NumberUtility.Decimal(value) });

#if !NET20
            else if (pt.Equals(typeof(Nullable<DateTime>))) setter.Invoke(record, new object[] { new Nullable<DateTime>((DateTime)value) });
            else if (pt.Equals(typeof(Nullable<bool>))) setter.Invoke(record, new object[] { new Nullable<bool>(NumberUtility.Boolean(value)) });
            else if (pt.Equals(typeof(Nullable<byte>))) setter.Invoke(record, new object[] { new Nullable<byte>(NumberUtility.Byte(value)) });
            else if (pt.Equals(typeof(Nullable<sbyte>))) setter.Invoke(record, new object[] { new Nullable<sbyte>(NumberUtility.SByte(value)) });
            else if (pt.Equals(typeof(Nullable<short>))) setter.Invoke(record, new object[] { new Nullable<short>(NumberUtility.Int16(value)) });
            else if (pt.Equals(typeof(Nullable<ushort>))) setter.Invoke(record, new object[] { new Nullable<int>(NumberUtility.UInt16(value)) });
            else if (pt.Equals(typeof(Nullable<int>))) setter.Invoke(record, new object[] { new Nullable<int>(NumberUtility.Int32(value)) });
            else if (pt.Equals(typeof(Nullable<uint>))) setter.Invoke(record, new object[] { new Nullable<uint>(NumberUtility.UInt32(value)) });
            else if (pt.Equals(typeof(Nullable<long>))) setter.Invoke(record, new object[] { new Nullable<long>(NumberUtility.Int64(value)) });
            else if (pt.Equals(typeof(Nullable<ulong>))) setter.Invoke(record, new object[] { new Nullable<ulong>(NumberUtility.UInt64(value)) });
            else if (pt.Equals(typeof(Nullable<float>))) setter.Invoke(record, new object[] { new Nullable<float>(NumberUtility.Single(value)) });
            else if (pt.Equals(typeof(Nullable<double>))) setter.Invoke(record, new object[] { new Nullable<double>(NumberUtility.Double(value)) });
            else if (pt.Equals(typeof(Nullable<decimal>))) setter.Invoke(record, new object[] { new Nullable<decimal>(NumberUtility.Decimal(value)) });
#endif

            else
            {
                try
                {
                    setter.Invoke(record, new object[] { value });
                    return true;
                }
                catch { }
            }
            return false;
        }

        /// <summary>解析 DataTable，填充没行到到指定的类型中，形成数组。</summary>
        /// <param name="table">将要读取的表。</param>
        /// <param name="compatible">当类型不同时，尝试转换以兼容。</param>
        /// <returns>由指定类型组成的数组。</returns>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        public static T[] Fill<T>(this DataTable table, bool compatible = true)
        {
            if (table == null) throw new ArgumentNullException(nameof(table), $"参数 {table} 无效。");
            var objects = Fill(table, typeof(T), compatible);
            var count = objects.Length;
            var array = new T[count];
            for (var i = 0; i < count; i++) array[i] = (T)objects[i];
            return array;
        }

        /// <summary>解析 DataTable，填充没行到到指定的类型中，形成数组。</summary>
        /// <param name="table">将要读取的表。</param>
        /// <param name="model">要填充的目标类型，必须是可实例化的引用类型。</param>
        /// <param name="compatible">当类型不同时，尝试转换以兼容。</param>
        /// <returns>由指定类型组成的数组。</returns>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="ArgumentException"></exception>
        public static object[] Fill(this DataTable table, Type model, bool compatible = true)
        {
            if (table == null) throw new ArgumentNullException(nameof(table), $"参数 {table} 无效。");
            if (model == null) throw new ArgumentNullException(nameof(model), $"参数 {model} 无效。");

            // 检查模型是否允许填充。
            var ts = TableStructure.Parse(model, true, true);
            if (ts == null) throw new ArgumentException($"无法填充到类型 {model.FullName} 中。");

            // 检查行数。
            var rows = table.Rows;
            var rowsCount = rows.Count;
            if (rowsCount < 1) return new object[0];

            // 确定数组。
            var array = new object[rowsCount];
            for (var i = 0; i < rowsCount; i++) array[i] = Activator.CreateInstance(model, true);

            // 检查列数。
            var columns = table.Columns;
            var columnsCount = columns.Count;
            if (columnsCount < 1) return array;

            // 解析表头，仅保留有名称的列。
            var sc = 0;
            var sfs = new string[columnsCount];
            var sts = new Type[columnsCount];
            var sis = new int[columnsCount];
            for (var i = 0; i < columnsCount; i++)
            {
                var column = columns[i];
                var key = column.ColumnName.Lower();
                if (string.IsNullOrEmpty(key)) continue;
                if (sfs.Contains(key)) continue;
                sfs[sc] = key;
                sts[sc] = column.DataType;
                sis[sc] = i;
                sc++;
            }
            if (sc < 1) return array;

            // 解析模型列。
            var cas = ts.Fillable;
            var dc = 0;
            var dfs = new string[cas.Length];
            var dts = new ColumnAttribute[cas.Length];
            for (var i = 0; i < cas.Length; i++)
            {
                var ca = cas[i];
                var key = ca.Field.Lower();
                if (string.IsNullOrEmpty(key)) continue;
                if (dfs.Contains(key)) continue;
                dfs[dc] = key;
                dts[dc] = ca;
                dc++;
            }
            if (dc < 1) return array;

            // 遍历、填充。
            for (var r = 0; r < rowsCount; r++)
            {
                var record = array[r];

                // 遍历 table 的列。
                for (var s = 0; s < sc; s++)
                {
                    var sf = sfs[s];

                    // 遍历 model 的列。
                    for (var d = 0; d < dc; d++)
                    {
                        var df = dfs[d];
                        if (df != sf) continue;

                        // 取值、填充。
                        var value = rows[r][sis[s]];
                        Fill(record, dts[d], sts[s], value, compatible);
                        break;
                    }
                }
            }

            return array;
        }

        static bool Fill(object record, ColumnAttribute ca, Type st, object value, bool compatible)
        {
            // 如果是 NULL 则忽略填充。
            if (value.IsNull()) return false;

            // 获取属性的类型，必须与 table 中的类型相同。
            var prop = ca.Property;
            if (prop.PropertyType == st)
            {
                prop.SetValue(record, value, null);
                return true;
            }

            // 类型不同且不需要兼容时，不填充。
            if (!compatible) return false;

            // 根据属性类型设置值。
            var pt = prop.PropertyType;
            if (pt.Equals(typeof(object))) prop.SetValue(record, value, null);
            else if (pt.Equals(typeof(byte[]))) prop.SetValue(record, (byte[])value, null);
            else if (pt.Equals(typeof(string))) prop.SetValue(record, value.ToString(), null);

            else if (pt.Equals(typeof(DateTime))) prop.SetValue(record, value, null);
            else if (pt.Equals(typeof(bool))) prop.SetValue(record, NumberUtility.Boolean(value), null);
            else if (pt.Equals(typeof(byte))) prop.SetValue(record, NumberUtility.Byte(value), null);
            else if (pt.Equals(typeof(sbyte))) prop.SetValue(record, NumberUtility.SByte(value), null);
            else if (pt.Equals(typeof(short))) prop.SetValue(record, NumberUtility.Int16(value), null);
            else if (pt.Equals(typeof(ushort))) prop.SetValue(record, NumberUtility.UInt16(value), null);
            else if (pt.Equals(typeof(int))) prop.SetValue(record, NumberUtility.Int32(value), null);
            else if (pt.Equals(typeof(uint))) prop.SetValue(record, NumberUtility.UInt32(value), null);
            else if (pt.Equals(typeof(long))) prop.SetValue(record, NumberUtility.Int64(value), null);
            else if (pt.Equals(typeof(ulong))) prop.SetValue(record, NumberUtility.UInt64(value), null);
            else if (pt.Equals(typeof(float))) prop.SetValue(record, NumberUtility.Single(value), null);
            else if (pt.Equals(typeof(double))) prop.SetValue(record, NumberUtility.Double(value), null);
            else if (pt.Equals(typeof(decimal))) prop.SetValue(record, NumberUtility.Decimal(value), null);

            else if (pt.Equals(typeof(Nullable<DateTime>))) prop.SetValue(record, new Nullable<DateTime>((DateTime)value), null);
            else if (pt.Equals(typeof(Nullable<bool>))) prop.SetValue(record, new Nullable<bool>(NumberUtility.Boolean(value)), null);
            else if (pt.Equals(typeof(Nullable<byte>))) prop.SetValue(record, new Nullable<byte>(NumberUtility.Byte(value)), null);
            else if (pt.Equals(typeof(Nullable<sbyte>))) prop.SetValue(record, new Nullable<sbyte>(NumberUtility.SByte(value)), null);
            else if (pt.Equals(typeof(Nullable<short>))) prop.SetValue(record, new Nullable<short>(NumberUtility.Int16(value)), null);
            else if (pt.Equals(typeof(Nullable<ushort>))) prop.SetValue(record, new Nullable<int>(NumberUtility.UInt16(value)), null);
            else if (pt.Equals(typeof(Nullable<int>))) prop.SetValue(record, new Nullable<int>(NumberUtility.Int32(value)), null);
            else if (pt.Equals(typeof(Nullable<uint>))) prop.SetValue(record, new Nullable<uint>(NumberUtility.UInt32(value)), null);
            else if (pt.Equals(typeof(Nullable<long>))) prop.SetValue(record, new Nullable<long>(NumberUtility.Int64(value)), null);
            else if (pt.Equals(typeof(Nullable<ulong>))) prop.SetValue(record, new Nullable<ulong>(NumberUtility.UInt64(value)), null);
            else if (pt.Equals(typeof(Nullable<float>))) prop.SetValue(record, new Nullable<float>(NumberUtility.Single(value)), null);
            else if (pt.Equals(typeof(Nullable<double>))) prop.SetValue(record, new Nullable<double>(NumberUtility.Double(value)), null);
            else if (pt.Equals(typeof(Nullable<decimal>))) prop.SetValue(record, new Nullable<decimal>(NumberUtility.Decimal(value)), null);

            else
            {
                try
                {
                    prop.SetValue(record, value, null);
                    return true;
                }
                catch { }
            }
            return false;
        }

        #endregion

        #region Record

        /// <summary>修复记录属性。</summary>
        public static void FixProperties(object record)
        {
            if (record == null) return;

            if (record is IRecord key)
            {
                if (string.IsNullOrEmpty(key.Key)) key.ResetKey();
            }

            if (record is IRecordMoment moment)
            {
                var now = moment.GenerateMoment();
                if (string.IsNullOrEmpty(moment.Created)) moment.Created = now;
                if (string.IsNullOrEmpty(moment.Updated)) moment.Updated = now;
            }
            if (record is IRecordStamp stamp)
            {
                var now = stamp.GenerateStamp();
                if (stamp.Created == 0L) stamp.Created = now;
                if (stamp.Updated == 0L) stamp.Updated = now;
            }
        }

        /// <summary>设置 Updated 属性。</summary>
        /// <returns>TRUE：设置成功；FALSE：设置失败。</returns>
        public static bool SetUpdated(object record)
        {
            if (record == null) return false;
            var setted = false;
            if (record is IRecordMoment moment)
            {
                moment.Updated = moment.GenerateMoment();
                setted = true;
            }
            if (record is IRecordStamp stamp)
            {
                stamp.Updated = stamp.GenerateStamp();
                setted = true;
            }
            return setted;
        }

        /// <summary>枚举带有 Table 特性的 <typeparamref name="T"/> 派生类型。</summary>
        public static Type[] EnumerateRecords<T>() where T : IRecord => EnumerateRecords(typeof(T));

        /// <summary>枚举带有 Table 特性的派生类型。</summary>
        /// <exception cref="ArgumentNullException"></exception>
        public static Type[] EnumerateRecords(Type baseType)
        {
            if (baseType == null) throw new ArgumentNullException(nameof(baseType));
            var assemblies = AppDomain.CurrentDomain.GetAssemblies();
            var builder = new ArrayBuilder<Type>();
            foreach (var assembly in assemblies)
            {
                var types = RuntimeUtility.GetTypes(assembly);
                foreach (var type in types)
                {
                    if (!EnumerateRecords(type, baseType)) continue;
                    if (builder.Contains(type)) continue;
                    builder.Add(type);
                }
            }
            return builder.Export();
        }

        static bool EnumerateRecords(Type type, Type @base)
        {
            if (type == null || @base == null) return false;
            if (type.IsAbstract) return false;
            if (!RuntimeUtility.Contains<TableAttribute>(type, false)) return false;
            if (type.Equals(@base)) return true;
            if (RuntimeUtility.IsInherits(type, @base)) return true;
            return false;
        }

        #endregion

        #region Query

        /// <summary>简单查询：取结果中第 0 列所有单元格的文本形式，可指定查询后关闭服务器连接，返回结果中不包含无效文本。</summary>
        /// <param name="source">数据库客户端。</param>
        /// <param name="sql">用于查询的 SQL 语句。</param>
        /// <param name="parameters">SQL 参数。</param>
        /// <exception cref="SqlException"></exception>
        public static string[] Column(this IDbAdo source, string sql, object parameters = null)
        {
            if (source == null) return new string[0];

            var pool = null as string[];
            var rows = 0;
            var count = 0;
            using (var query = source.Query(sql, parameters))
            {
                if (!query.Success) throw new SqlException(query, sql);

                rows = query.Rows;
                if (rows < 1) return new string[0];

                pool = new string[rows];
                for (int i = 0; i < rows; i++)
                {
                    var cell = TextUtility.Trim(query.Text(i, 0));
                    if (string.IsNullOrEmpty(cell)) continue;

                    pool[count] = cell;
                    count++;
                }
            }

            if (count < 1) return new string[0];
            if (count == rows) return pool;

            var array = new string[count];
            Array.Copy(pool, 0, array, 0, count);
            return array;
        }

        /// <summary>简单查询：取结果中第 0 行、第 0 列单元格中的文本，可指定查询后关闭服务器连接。</summary>
        /// <param name="dbClient">数据库客户端。</param>
        /// <param name="sql">用于查询的 SQL 语句。</param>
        /// <param name="parameters">SQL 参数。</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="SqlException"></exception>
        public static string Cell(this IDbAdo dbClient, string sql, object parameters = null)
        {
            if (dbClient == null) throw new ArgumentNullException(nameof(dbClient));
            if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql));

            using (var query = dbClient.Query(sql, parameters))
            {
                if (!query.Success) throw new SqlException(query, sql);
                var value = TextUtility.Trim(query.Text(0, 0));
                return value;
            }
        }

        /// <summary>查询。</summary>
        /// <param name="dbClient">数据库连接。</param>
        /// <param name="sql">SQL 语句。</param>
        /// <param name="parameters">SQL 参数。</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static IQuery Query(this IDbAdo dbClient, string sql, IEnumerable<KeyValuePair<string, object>> parameters)
        {
            if (dbClient == null) throw new ArgumentNullException(nameof(dbClient));
            if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql));

            var ps = Parameters(dbClient, sql, parameters);
            return dbClient.Query(sql, ps);
        }

        /// <summary>查询。</summary>
        /// <param name="dbClient">数据库连接。</param>
        /// <param name="sql">SQL 语句。</param>
        /// <param name="parameters">参数容器，每个属性表示一个 SQL 参数。此方法将会自动补足参数名称的 @ 前缀。</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static IQuery Query(this IDbAdo dbClient, string sql, object parameters = null)
        {
            if (dbClient == null) throw new ArgumentNullException(nameof(dbClient));
            if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql));

            if (parameters is IEnumerable<KeyValuePair<string, object>> kvps)
            {
                var ps = Parameters(dbClient, sql, kvps);
                return dbClient.Query(sql, ps);
            }
            else
            {
                var ps = ParametersByProperites(dbClient, sql, parameters);
                return dbClient.Query(sql, ps);
            }
        }

        /// <summary>执行 SELECT 语句，获取查询结果。</summary>
        /// <param name="connection">数据库连接。</param>
        /// <param name="transaction">事务。</param>
        /// <param name="sql">SQL 语句。</param>
        /// <param name="parameters">参数。</param>
        /// <param name="timeout">超时秒数。</param>
        /// <returns>查询结果。</returns>
        public static DataTable Query(this IDbConnection connection, IDbTransaction transaction, string sql, IEnumerable<IDbDataParameter> parameters = null, int timeout = 3600)
        {
            if (connection == null) throw new ArgumentNullException(nameof(connection));
            if (string.IsNullOrEmpty(sql)) throw new ArgumentNullException(nameof(sql));

            if (connection.State != ConnectionState.Open) connection.Open();
            using (var command = connection.CreateCommand())
            {
                if (transaction != null) command.Transaction = transaction;
                if (timeout > 0) command.CommandTimeout = timeout;
                command.CommandText = sql;
                if (parameters != null)
                {
                    foreach (var parameter in parameters)
                    {
                        if (parameter == null) continue;
                        command.Parameters.Add(parameter);
                    }
                }

                using (var reader = command.ExecuteReader())
                {
                    var table = new DataTable();
                    table.Load(reader);
                    return table;
                }
            }
        }


        /// <summary>查询。</summary>
        /// <param name="dbClient">数据库连接。</param>
        /// <param name="sql">SQL 语句。</param>
        /// <param name="parameters">参数容器，每个属性表示一个 SQL 参数。此方法将会自动补足参数名称的 @ 前缀。</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="NotImplementedException"></exception>
        public static T[] Query<T>(this IDbOrm dbClient, string sql, object parameters = null) where T : class, new()
        {
            if (dbClient == null) throw new ArgumentNullException(nameof(dbClient));
            if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql));

            if (dbClient is IDbAdo ado)
            {
                if (parameters is IEnumerable<KeyValuePair<string, object>> kvps)
                {
                    var ps = Parameters(ado, sql, kvps);
                    return dbClient.Query<T>(sql, ps);
                }
                else
                {
                    var ps = ParametersByProperites(ado, sql, parameters);
                    return dbClient.Query<T>(sql, ps);
                }
            }
            else
            {
                throw new NotImplementedException($"连接未实现 {nameof(IDbAdo)} 接口，无法创建参数。");
            }
        }


        #endregion

        #region Execute

        /// <summary>执行 SQL 语句，并加入参数。</summary>
        /// <exception cref="ArgumentNullException"></exception>
        public static IExecute Execute(this IDbAdo dbClient, string sql, IEnumerable<KeyValuePair<string, object>> parameters, bool autoTransaction = false)
        {
            if (dbClient == null) throw new ArgumentNullException(nameof(dbClient));
            if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql));

            var ps = Parameters(dbClient, sql, parameters);
            return dbClient.Execute(sql, ps, autoTransaction);
        }

        /// <summary>执行 SQL 语句，并加入参数。</summary>
        /// <param name="dbClient">数据库连接。</param>
        /// <param name="sql">SQL 语句。</param>
        /// <param name="parameters">参数容器，每个属性表示一个 SQL 参数。此方法将会自动补足参数名称的 @ 前缀。</param>
        /// <param name="autoTransaction">自动使用事务。</param>
        /// <exception cref="ArgumentNullException"></exception>
        public static IExecute Execute(this IDbAdo dbClient, string sql, object parameters = null, bool autoTransaction = false)
        {
            if (dbClient == null) throw new ArgumentNullException(nameof(dbClient));
            if (sql.IsEmpty()) throw new ArgumentNullException(nameof(sql));

            if (parameters is IEnumerable<KeyValuePair<string, object>> kvps)
            {
                var ps = Parameters(dbClient, sql, kvps);
                return dbClient.Execute(sql, ps, autoTransaction);
            }

            {
                var ps = ParametersByProperites(dbClient, sql, parameters);
                return dbClient.Execute(sql, ps, autoTransaction);
            }
        }

        /// <summary>执行 SQL 语句，获取影响的行数。</summary>
        /// <param name="connection">数据库连接。</param>
        /// <param name="transaction">事务。</param>
        /// <param name="sql">SQL 语句。</param>
        /// <param name="parameters">参数。</param>
        /// <param name="timeout">超时秒数。</param>
        /// <returns>行数。</returns>
        public static int Execute(this IDbConnection connection, IDbTransaction transaction, string sql, IEnumerable<IDbDataParameter> parameters = null, int timeout = 3600)
        {
            if (connection == null) throw new ArgumentNullException(nameof(connection));
            if (string.IsNullOrEmpty(sql)) throw new ArgumentNullException(nameof(sql));

            if (connection.State != ConnectionState.Open) connection.Open();
            using (var command = connection.CreateCommand())
            {
                if (transaction != null) command.Transaction = transaction;
                if (timeout > 0) command.CommandTimeout = timeout;
                command.CommandText = sql;
                if (parameters != null)
                {
                    foreach (var parameter in parameters)
                    {
                        if (parameter == null) continue;
                        command.Parameters.Add(parameter);
                    }
                }

                var rows = command.ExecuteNonQuery();
                return rows;
            }
        }

        #endregion

        #region Transaction

        /// <summary>启动事务，执行指定的过程并在完成后提交事务。若过程被异常打断，则回滚事务。</summary>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="SqlException"></exception>
        public static void InTransaction(this IDbAdo source, Action action)
        {
            // 检查参数。
            if (source == null) throw new ArgumentNullException(nameof(source), "数据源无效。");
            if (action == null) throw new ArgumentNullException(nameof(action), "没有指定要在事物中执行的程序。");

            InTransaction<object>(source, () =>
            {
                action.Invoke();
                return null;
            });
        }

        /// <summary>启动事务，执行指定的过程并在完成后提交事务。若过程被异常打断，则回滚事务。</summary>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="SqlException"></exception>
        public static T InTransaction<T>(this IDbAdo source, Func<T> func)
        {
            // 检查参数。
            if (source == null) throw new ArgumentNullException(nameof(source), "数据源无效。");
            if (func == null) throw new ArgumentNullException(nameof(func), "没有指定要在事物中执行的程序。");

            // 已经存在事务。
            if (source.Transaction != null) return func.Invoke();

            // 启动事务。
            var begin = source.Begin();
            if (begin.NotEmpty()) throw new SqlException("无法启动事务：" + begin);

            var result = default(T);
            var success = false;
            try
            {
                // 在事务内运行。
                result = func.Invoke();
                success = true;
            }
            finally
            {
                if (success)
                {
                    // 执行成功，提交事务。
                    var commit = source.Commit();
                    if (!string.IsNullOrEmpty(commit)) throw new SqlException(commit);
                }
                else
                {
                    // 执行失败，回滚事务。
                    try { source.Rollback(); } catch { }
                }
            }

            return result;
        }

        #endregion

        #region Parameter

        /// <exception cref="ArgumentNullException"></exception>
        static List<IDataParameter> ParametersByProperites(IDbAdo dbClient, string sql, object parameters)
        {
            if (dbClient == null) throw new ArgumentNullException(nameof(dbClient));
            if (parameters == null) return null;

            var lsql = sql.Lower();

            var type = parameters.GetType();
            var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public);
            var count = properties.Length;
            var dict = new Dictionary<string, object>(count);
            for (var i = 0; i < count; i++)
            {
                var property = properties[i];

                // 属性必须能够获取值。
                var getter = property.GetGetMethod();
                if (getter == null) continue;

                // 属性值必须有效。
                var name = property.Name;
                if (name.IsEmpty()) continue;

                // 属性不可重复。
                if (!name.StartsWith("@")) name = "@" + name;
                if (dict.ContainsKey(name)) continue;

                // SQL 语句中必须包含此参数。
                var lname = name.Lower();
                if (!lsql.Contains(lname)) continue;

                // 加入字典。
                var value = getter.Invoke(parameters, null);
                dict.Add(name, value);
            }
            if (dict.Count < 1) return null;

            var ps = new List<IDataParameter>();
            foreach (var kvp in dict)
            {
                var p = dbClient.Parameter(kvp.Key, kvp.Value);
                ps.Add(p);
            }
            return ps;
        }

        /// <exception cref="ArgumentNullException"></exception>
        static List<IDataParameter> Parameters(IDbAdo dbClient, string sql, IEnumerable<KeyValuePair<string, object>> parameters)
        {
            if (dbClient == null) throw new ArgumentNullException(nameof(dbClient));
            if (parameters == null) return null;

            var lsql = sql.Lower();
            var names = new List<string>(20);
            var ps = new List<IDataParameter>(20);
            foreach (var kvp in parameters)
            {
                var name = kvp.Key;
                if (name.IsEmpty()) continue;

                // 属性不可重复。
                if (!name.StartsWith("@")) name = "@" + name;
                if (names.Contains(name)) continue;

                // SQL 语句中必须包含此参数。
                var lname = name.Lower();
                if (!lsql.Contains(lname)) continue;

                var p = dbClient.Parameter(name, kvp.Value);
                ps.Add(p);
                names.Add(name);
            }
            return ps;
        }

        #endregion

        #region SQL

        /// <summary>对文本转义，符合 SQL 安全性。可根据字段类型限制 UTF-8 字节数，默认为 0 时不限制字节数。</summary>
        public static string Escape(this string text, int bytes = 0)
        {
            if (text.IsEmpty()) return "";

            var t = text ?? "";
            t = t.Replace("\\", "\\\\");
            t = t.Replace("'", "\\'");
            t = t.Replace("\n", "\\n");
            t = t.Replace("\r", "\\r");
            t = t.Replace("\b", "\\b");
            t = t.Replace("\t", "\\t");
            t = t.Replace("\f", "\\f");

            if (bytes > 5)
            {
                if (t.Bytes(Encoding.UTF8).Length > bytes)
                {
                    while (true)
                    {
                        t = t.Substring(0, t.Length - 1);
                        if (t.Bytes(Encoding.UTF8).Length <= (bytes - 4)) break;
                    }
                    t = t + " ...";
                }
            }

            return t;
        }

        /// <summary>限定名称文本，只允许包含字母、数字和下划线。</summary>
        public static string SafeName(this string name) => TextUtility.Restrict(name, "0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz");

        #endregion

        #region 数据模型 -> DataTable

        /// <summary>将多个实体元素转换为 DataTable。</summary>
        /// <typeparam name="T">实体元素的类型。</typeparam>
        /// <param name="items">实体元素。</param>
        /// <param name="tableName">设置 <see cref="DataTable"/> 的名称。</param>
        /// <exception cref="ArgumentNullException"></exception>
        /// <exception cref="DuplicateNameException"></exception>
        /// <exception cref="InvalidExpressionException"></exception>
        public static DataTable DataTable<T>(this IEnumerable<T> items, string tableName = null)
        {
            if (items == null) throw new ArgumentNullException(nameof(items));

            // 解析表结构。
            var it = typeof(T);
            var ts = TableStructure.Parse(it, true, true);
            if (ts == null || ts.Columns == null || ts.Columns.Length < 1)
            {
                foreach (var item in items)
                {
                    if (item == null) continue;

                    var itemType = item.GetType();
                    ts = TableStructure.Parse(itemType, true, true);
                    if (ts == null) throw new TypeLoadException($"无法解析 {itemType.FullName} 的结构。");
                    it = itemType;
                    break;
                }
                if (ts == null) throw new TypeLoadException($"无法解析 {it.FullName} 的结构。");
            }
            var cas = ts.Columns;
            var width = cas.Length;
            if (width < 1) throw new TypeLoadException($"类型 {it.FullName} 的结构中没有列。");

            // 初始化列。
            var table = new DataTable();
            var pis = new PropertyInfo[width];
            var fts = new Type[width];
            for (var i = 0; i < width; i++)
            {
                var ca = cas[i];
                var pi = ca.Property;
                var pt = pi.PropertyType;

                pis[i] = pi;
                var ft = pt;
                if (pt.IsGenericType && pt.GetGenericTypeDefinition() == typeof(Nullable<>))
                {
                    pt.GetGenericArguments();
                    ft = Nullable.GetUnderlyingType(pt);
                }
                fts[i] = ft;

                var column = new DataColumn(ca.Field, ft);
                column.AllowDBNull = true;
                table.Columns.Add(column);
            }

            // 添加行。
            foreach (var item in items)
            {
                if (item == null) continue;
                var values = new ArrayBuilder<object>(width);
                for (var i = 0; i < width; i++)
                {
                    var value = pis[i].GetValue(item, null);
                    if (value is DateTime dt)
                    {
                        if (dt.Year < 1753)
                        {
                            values.Add(DBNull.Value);
                            continue;
                        }
                    }
                    values.Add(value);
                }
                table.Rows.Add(values.Export());
            }

            if (tableName.NotEmpty()) table.TableName = tableName;
            else if (ts.TableName.NotEmpty()) table.TableName = ts.TableName;

            return table;
        }

        #endregion

        #region DataTable 序列化

        /// <summary>转换 <see cref="System.Data.DataTable"/> 到 <see cref="ObjectSet{T}"/> 数组，每行记录为一个 ObjectSet 对象。</summary>
        /// <returns>当参数 table 无效时返回 0 长度的 <see cref="ObjectSet{T}"/> 数组。</returns>
        public static ObjectSet[] ObjectSet(this DataTable table)
        {
            if (table == null) return new ObjectSet[0];

            var columns = table.Columns.Count;
            var fields = new string[columns];
            for (var c = 0; c < columns; c++) fields[c] = table.Columns[c].ColumnName;

            var rows = table.Rows.Count;
            var dicts = new Dictionary<string, object>[rows];
            for (var r = 0; r < table.Rows.Count; r++)
            {
                var dict = new Dictionary<string, object>(columns);
                for (var c = 0; c < columns; c++)
                {
                    var field = fields[c];
                    if (string.IsNullOrEmpty(field)) continue;
                    if (dict.ContainsKey(field)) continue;
                    var v = table.Rows[r][c];
                    if (v.IsNull()) v = null;
                    dict.Add(field, v);
                }
                dicts[r] = dict;
            }

            var oss = new ObjectSet[rows];
            for (var i = 0; i < rows; i++) oss[i] = new ObjectSet(dicts[i]);
            return oss;
        }

        /// <summary>转换为 Json 对象。</summary>
        public static Json ToJson(this DataTable table, Func<DateTime, string> dateTimeFormatter = null)
        {
            if (table == null) return null;

            var columns = ToJson(table.Columns);
            var rows = ToJson(table.Rows, dateTimeFormatter);

            var jsonObject = Json.NewObject();
            jsonObject.SetProperty("columns", columns);
            jsonObject.SetProperty("rows", rows);
            return jsonObject;
        }

        /// <summary>转换为 Json 对象。</summary>
        public static Json ToJson(this DataColumnCollection columns)
        {
            if (columns == null) return null;

            var json = Json.NewArray();
            var count = columns.Count;
            for (var c = 0; c < count; c++)
            {
                var dc = columns[c];
                var column = Json.NewObject();
                column.SetProperty("name", dc.ColumnName);
                column.SetProperty("type", dc.DataType.FullName);
                json.AddItem(column);
            }
            return json;
        }

        /// <summary>转换为 Json 对象。</summary>
        public static Json ToJson(this DataRowCollection rows, Func<DateTime, object> dateTimeFormatter = null)
        {
            if (rows == null) return null;

            var json = Json.NewArray();
            var count = rows.Count;
            for (var r = 0; r < count; r++)
            {
                json.AddItem(ToJson(rows[r], dateTimeFormatter));
            }
            return json;
        }

        /// <summary>转换为 Json 对象。</summary>
        public static Json ToJson(this DataRow row, Func<DateTime, object> dateTimeFormatter = null)
        {
            if (row == null) return null;
            var cells = row.ItemArray;
            var count = cells.Length;

            var json = Json.NewArray();
            for (var c = 0; c < count; c++)
            {
                var value = cells[c];
                if (value == null || value.Equals(DBNull.Value))
                {
                    json.AddItem();
                    continue;
                }

                if (value is DateTime vDateTime)
                {
                    if (dateTimeFormatter == null)
                    {
                        json.AddItem(Json.SerializeDateTime(vDateTime));
                        continue;
                    }
                    else
                    {
                        value = dateTimeFormatter.Invoke(vDateTime);
                        if (value == null || value.Equals(DBNull.Value))
                        {
                            json.AddItem();
                            continue;
                        }
                    }
                }

                if (value is string @string) json.AddItem(@string);
                else if (value is byte @byte) json.AddItem(@byte);
                else if (value is short @short) json.AddItem(@short);
                else if (value is int @int) json.AddItem(@int);
                else if (value is long @long) json.AddItem(@long);
                else if (value is float @float) json.AddItem(@float);
                else if (value is double @double) json.AddItem(@double);
                else if (value is decimal @decimal) json.AddItem(@decimal);
                else if (value is bool @bool) json.AddItem(@bool);
                else if (value is byte[] bytes) json.AddItem(bytes.Base64());
                else json.AddItem(TextUtility.Text(value));
            }
            return json;
        }

        /// <summary>转换 <see cref="DataTable"/> 为 CSV 文本，不存在表时返回 NULL 值。可指定是否包含表头。</summary>
        public static string Csv(DataTable table, bool withHead = false)
        {
            if (table == null) return null;

            var columns = table.Columns.Count;
            if (columns < 1) return "";
            var sb = new StringBuilder();
            if (withHead)
            {
                for (var c = 0; c < columns; c++)
                {
                    var v = table.Columns[c].ColumnName;
                    CsvCell(sb, c, v);
                }
            }

            var rows = table.Rows.Count;
            for (var r = 0; r < rows; r++)
            {
                var row = table.Rows[r];
                if (withHead || r > 0) sb.Append("\r\n");
                for (var c = 0; c < columns; c++) CsvCell(sb, c, row[c]);
            }

            return sb.ToString();
        }

        private static void CsvCell(StringBuilder sb, int c, object v)
        {
            if (c > 0) sb.Append(",");
            if (v == null || v.Equals(DBNull.Value)) return;

            if (v is bool @bool)
            {
                sb.Append(@bool ? "TRUE" : "FALSE");
                return;
            }

            if (v is DateTime @datetime)
            {
                sb.Append(@datetime.Lucid());
                return;
            }

            if (v is byte || v is sbyte || v is short || v is ushort || v is int || v is uint || v is long || v is ulong || v is float || v is double || v is decimal)
            {
                sb.Append(v.ToString());
                return;
            }

            if (v is char)
            {
                sb.Append((char)v);
                return;
            }

            var s = (v is string @string) ? @string : v.ToString();
            var length = s.Length;
            if (length < 1) return;

            var quote = false;
            var comma = false;
            var newline = false;
            for (var i = 0; i < length; i++)
            {
                var @char = s[i];
                if (@char == '\"') quote = true;
                else if (@char == ',') comma = true;
                else if (@char == '\r') newline = false;
                else if (@char == '\n') newline = false;
            }

            if (quote || comma || newline)
            {
                sb.Append("\"");
                s = s.Replace("\"", "\"\"");
                sb.Append(s);
                sb.Append("\"");
            }
            else sb.Append(s);
        }

        #endregion

        #region DataTable 快捷操作

        /// <summary>获取默认表中指定单元格的内容。</summary>
        /// <param name="table">数据表。</param>
        /// <param name="rowIndex">行索引，从 0 开始。</param>
        /// <param name="columnIndex">列索引，从 0 开始。</param>
        public static object Value(this DataTable table, int rowIndex, int columnIndex)
        {
            if (table != null)
            {
                if (rowIndex >= 0 && rowIndex < table.Rows.Count)
                {
                    if (columnIndex >= 0 && columnIndex < table.Columns.Count)
                    {
                        var value = table.Rows[rowIndex][columnIndex];
                        if (value == null || value.Equals(DBNull.Value)) return null;
                        return value;
                    }
                }
            }
            return null;
        }

        /// <summary>获取默认表中指定单元的内容。</summary>
        /// <param name="table">数据表。</param>
        /// <param name="rowIndex">行索引，从 0 开始。</param>
        /// <param name="columnName">列名称/字段名称，此名称不区分大小写。</param>
        public static object Value(this DataTable table, int rowIndex, string columnName)
        {
            if (table != null && !string.IsNullOrEmpty(columnName))
            {
                if ((rowIndex < table.Rows.Count) && (rowIndex >= 0))
                {
                    try
                    {
                        var value = table.Rows[rowIndex][columnName];
                        if (value == null || value.Equals(DBNull.Value)) return null;
                        return value;
                    }
                    catch { }
                }
            }
            return null;
        }

        /// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
        public static Class<DateTime> DateTime(this DataTable table, int row, int column) => table == null ? null : ClockUtility.DateTime(table.Value(row, column));

        /// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
        public static Class<DateTime> DateTime(this DataTable table, int row, string column) => table == null ? null : ClockUtility.DateTime(table.Value(row, column));

        /// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
        public static Int32 Int32(this DataTable table, int row, int column) => table == null ? 0 : NumberUtility.Int32(table.Value(row, column));

        /// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
        public static Int32 Int32(this DataTable table, int row, string column) => table == null ? 0 : NumberUtility.Int32(table.Value(row, column));

        /// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
        public static Int64 Int64(this DataTable table, int row, int column) => table == null ? 0L : NumberUtility.Int64(table.Value(row, column));

        /// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
        public static Int64 Int64(this DataTable table, int row, string column) => table == null ? 0L : NumberUtility.Int64(table.Value(row, column));

        /// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
        public static Decimal Decimal(this DataTable table, int row, int column) => table == null ? 0M : NumberUtility.Decimal(table.Value(row, column));

        /// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
        public static Decimal Decimal(this DataTable table, int row, string column) => table == null ? 0M : NumberUtility.Decimal(table.Value(row, column));

        /// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>>
        public static Double Double(this DataTable table, int row, int column) => table == null ? 0D : NumberUtility.Double(table.Value(row, column));

        /// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>>
        public static Double Double(this DataTable table, int row, string column) => table == null ? 0D : NumberUtility.Double(table.Value(row, column));

        /// <summary>获取默认表中指定单元格的内容。从第 0 行第 0 列开始。</summary>
        public static string Text(this DataTable table, int row, int column) => table == null ? null : TextUtility.Text(table.Value(row, column));

        /// <summary>获取默认表中指定单元格的内容。从第 0 行开始。</summary>
        public static string Text(this DataTable table, int row, string column) => table == null ? null : TextUtility.Text(table.Value(row, column));

        #endregion

        #region Dynamic

#if NET40_OR_GREATER

        /// <summary>转换 ObjectSet 数组为 dynamic 数组。</summary>
        public static dynamic[] Dynamic(this ObjectSet[] oss)
        {
            if (oss == null) return new dynamic[0];

            var eos = oss.Expando();
            var ds = new dynamic[eos.Length];
            eos.CopyTo(ds, 0);
            return ds;
        }

#endif

        #endregion

        #region 表达式计算

        /// <summary>计算文本表达式。</summary>
        public static object Compute(string expression)
        {
            using (var table = new DataTable())
            {
                var result = table.Compute(expression, null);
                if (result.IsNull()) return null;
                return result;
            }
        }

        #endregion

    }

}
